package com.lifeonwalden.fincalc.utils.date;

import com.lifeonwalden.fincalc.utils.date.bean.DateIntersectionData;

import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.function.BiFunction;
import java.util.function.Function;

/**
 * 日期序列数据处理
 */
public interface DateSeriesUtils {
    /**
     * 基于给定日期序列数据，创建一个完整自然日序列的序列数据。对于缺失日期，以缺失日期前的数据为基础进行克隆。
     *
     * @param dataList        升序的日期序列数据
     * @param fetchDate       从序列数据对象中获得日期
     * @param cloneAndSetDate 生成新一天的数据（克隆某天的数据并设置新的日期，深浅克隆依据业务需要自己定)
     * @param <T>
     * @return
     */
    static <T> List<T> createNaturalDateSeries(List<T> dataList, Function<T, Date> fetchDate, BiFunction<T, Date, T> cloneAndSetDate) {
        List<T> newList = new ArrayList<>();
        Instant date = fetchDate.apply(dataList.get(0)).toInstant();
        for (T data : dataList) {
            Date currentDate = fetchDate.apply(data);
            if (currentDate.getTime() != date.toEpochMilli()) {
                InnerDateSeriesFunction.fillToNaturalSeries(newList, newList.get(newList.size() - 1), date, currentDate, cloneAndSetDate);
                date = currentDate.toInstant().plus(1, ChronoUnit.DAYS);
            } else {
                date = date.plus(1, ChronoUnit.DAYS);
            }
            newList.add(data);
        }

        return newList;
    }

    /**
     * 将给定日期序列数据补充成完整自然日序列的序列数据。对于缺失日期，以缺失日期前的数据为基础进行克隆。
     *
     * @param dataList        升序的日期序列数据
     * @param fetchDate       从序列数据对象中获得日期
     * @param cloneAndSetDate 生成新一天的数据（克隆某天的数据并设置新的日期，深浅克隆依据业务需要自己定)
     * @param <T>
     * @return
     */
    static <T> void fillToNaturalDateSeries(List<T> dataList, Function<T, Date> fetchDate, BiFunction<T, Date, T> cloneAndSetDate) {
        List<T> newList = createNaturalDateSeries(dataList, fetchDate, cloneAndSetDate);
        dataList.clear();
        dataList.addAll(newList);
    }

    static void makeIntersectionSet(List<DateIntersectionData> dateSetList) {
        int[] idx = new int[dateSetList.size()];
        Arrays.fill(idx, 0);

        DateIntersectionData firstDateSet = dateSetList.get(0);
        int firstSetSize = firstDateSet.getDataList().size();

        for (int firstSetIdx = 0; firstSetIdx < firstSetSize; firstSetIdx++) {
            Date baseDate = (Date) firstDateSet.getDateFetcher().apply(firstDateSet.getDataList().get(firstSetIdx));

            boolean hasIntersectionDate = true;
            boolean noMoreDataInSomeSet = false;
            for (int setIdx = 1; setIdx < idx.length; setIdx++) {
                DateIntersectionData dateSet = dateSetList.get(setIdx);

                do {
                    if (idx[setIdx] >= dateSet.getDataList().size()) {
                        noMoreDataInSomeSet = true;
                        break;
                    }

                    Date date = (Date) dateSet.getDateFetcher().apply(dateSet.getDataList().get(idx[setIdx]));
                    int compareResult = date.compareTo(baseDate);
                    if (0 == compareResult) {
                        break;
                    } else if (compareResult > 0) {
                        hasIntersectionDate = false;
                        break;
                    } else {
                        idx[setIdx] = idx[setIdx] + 1;
                    }
                } while (true);

                if (false == hasIntersectionDate || noMoreDataInSomeSet) {
                    break;
                }
            }

            if (noMoreDataInSomeSet) {
                return;
            } else {
                if (hasIntersectionDate) {
                    firstDateSet.getResultList().add(firstDateSet.getDataList().get(firstSetIdx));
                    for (int setIdx = 1; setIdx < idx.length; setIdx++) {
                        DateIntersectionData dateSet = dateSetList.get(setIdx);
                        dateSet.getResultList().add(dateSet.getDataList().get(idx[setIdx]));

                        idx[setIdx] = idx[setIdx] + 1;
                    }
                }
            }
        }
    }
}
